#! /usr/bin/env ruby
# prunexyd.rb - IRC Proxy daemon
# Copyright (C) 2005-2009 Akira TAGOH

# Authors:
#   Akira TAGOH  <akira@tagoh.org>

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.

require 'etc'
require 'fileutils'
require 'optparse'
require 'yaml'
require 'prune/debug'
require 'prune/decoration'
require 'prune/irc'
require 'prune/message'
require 'prune/parser'
require 'prune/socket'
require 'prune/version'


module PRUNE

  class IRCProxy < PRUNE::IRC

    def initialize
      super

      def @state.dup
        retval = super

        [:@state].each do |v|
          retval.instance_variable_set(v, eval(sprintf("%s.dup", v)))
        end

        retval
      end # def @state.dup

      @listeninfo = nil
      @proxysigids = {}
      @connectors = {}
      @conninfo = {}
      @lstate = {}
      @lsockets = {}
      @suffixcount = 0
      @suffixreversemap = {}
      @suffixmap = {}
      @disconntimer = 60
    end # def initialize

    attr_reader :connectors

    def open(conninfo, listeninfo)
      raise TypeError, sprintf("Can't convert %s into Array", conninfo.class) unless conninfo.kind_of?(Array)

      begin
        info("Listening on %s/%s", listeninfo.host, listeninfo.port)
        socket = PRUNE::TCPServer.new(listeninfo.host, listeninfo.port)
        @listeninfo = listeninfo
        @sdelegate = PRUNE::ServerSocketDelegator.new(self, socket)
      rescue IOError, SocketError, Errno::EPIPE, Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ETIMEDOUT, Errno::EHOSTUNREACH
        info("Failed to listen on %s/%s", listeninfo.host, listeninfo.port)
        return false
      end

      if @listeninfo.options.has_key?('Charset') then
        @charset.default = @listeninfo.options['Charset']
      end

      @state.nick = @listeninfo.nick
      @state.user = @listeninfo.user
      @state.name = @listeninfo.name
      @state.host = @sdelegate.addr[2]
      @value[:listener_timer] = {}
      @value[:ping] = {}
      @proxysigids[:Listen] = @eventmgr.auto_registration(@sdelegate.key, self, '_proxy_listen', PRUNE::EventManager::PRIOR_SYS_SYNC_HIGH)

      super(@sdelegate, @sdelegate.key, PRUNE::IRC::MODE_LISTEN, @listeninfo.options)
      # basically it's useless. so unregistering
      @eventmgr.unregister(@ids[:Async])
      @ids.delete(:Async)

      @state.listened = true
      connq = PRUNE::Queue.new

      until @sdelegate.closed? do
        if @state.listen? then
          conninfo.each do |ci|
            if !ci[0].options.has_key?('Identity') || ci[0].options['Identity'].nil? then
              ci[0].options['Identity'] = "#{@suffixcount}"
              @suffixcount += 1
            end
            suffix = sprintf("@%s", ci[0].options['Identity'])

            unless connq.include?(suffix) then
              connq << suffix

              if @suffixreversemap.has_key?(suffix) then
                raise ArgumentError, sprintf("Detected duplicate Identity property `%s' for %s/%s. which is in use for `%s'", suffix, ci.host, ci.port, @suffixreversemap[suffix])
              end

              Thread.new do
                c = PRUNE::IRCConnector.new

                key = sprintf("%s:%s:%s:%x", ci[0].options['Identity'], ci[0].host, ci[0].port, c.object_id)
                ids = @eventmgr.auto_registration(key, self, '_proxy_conn', PRUNE::EventManager::PRIOR_SYS_SYNC_NORMAL)
                begin
                  v = c.open(ci, key)
                end while v == false

                Thread.exclusive do
                  @proxysigids[key] = ids
                  @conninfo[key] = ci
                  @connectors[key] = c
                  @suffixmap[key] = suffix
                  @suffixreversemap[suffix] = key
                  @parser.suffix = @suffixmap.values.join('|')
                end
                Thread.exit
              end
            end
          end
          @connectors.each do |key, value|
            value.emit(key, :Async, nil, value.state)
          end
          @eventmgr.emit(@sdelegate.key, :Async, nil, @state)
          yield @eventmgr, @state, @sdelegate.key
        else
          # nothing to do
        end
        sleep 1
      end # until @sdelegate.closed?
      close
      true
    end # def open

    def close
      return false unless @state.listen?

      @eventmgr.auto_unregistration(@proxysigids)
      @proxysigids = {}
      @connectors.each do |k, v|
        info("Closing the connection `%s'", k)
        v.close
      end
      @connectors.clear
      @conninfo.clear

      super
    end # def close

    def is_connector?(key)
      @connectors.has_key?(key)
    end # def is_connector?

    def is_listener?(key)
      @sdelegate.key == key || @lstate.has_key?(key)
    end # def is_listener?

    def synchronize(mode = true)
      @eventmgr.synchronize(mode) do |x|
        yield self
      end
    end # def synchronize

    protected

    def _proxy_conn_disconnected(ret, key, *args)
      ci = @connectors[key]
      # send PART message to clients
      sfx = @suffixmap[key]
      ci.state.channels.each do |ch|
        cinfo = ci.instance_variable_get(:@conninfo)
        ret.emit_message(key, :Received, false,
                         PRUNE::Message::PART.new(ch,
                                                  sprintf("Disconnected from %s:%s", cinfo.host, cinfo.port)))
      end

      # FIXME: reconnect

      false
    end # def _proxy_conn_disconnected

    def _proxy_conn_received(ret, key, *args)
      return false if args[0].kind_of?(String)

      msg = args[0]

      if self.is_connector?(key) then
        # update nick name as needed
        if msg.nick == @connectors[key].state.nick then
          msg.nick = @state.nick
        end
        # add the suffix
        msg.suffix = @suffixmap[key]
        # send the message to the master listener to take care of plugins.
        # FIXME
        ret.emit_message(@sdelegate.key, :Sent, false, msg)
        # delegate messages to clients
        @lstate.dup.each do |k, v|
          ret.emit_message(k, :Sent, false, msg)
        end
      else
        bug("Got :Received signal from the unknown origin: %s", key)
      end

      false
    end # def _proxy_conn_received

    # RPL_CHANNELMODEIS
    def _proxy_conn_324_received(ret, key, *args)
      msg = args[0]
      msg.suffix = @suffixmap[key]

      if msg.channel.nil? then
        bug("Empty channel at RPL_CHANNELMODEIS: %s", msg)
      elsif !@state.channels.include?(msg.channel({:suffix=>true})) then
        bug("Unknown channel at RPL_CHANNELMODEIS: %s, but %s", msg.channel({:suffix=>true}), @state.channels)
      else
        cs = @state.channel(msg.channel({:suffix=>true}))
        cs.clear_mode
        cs.set_mode(*msg.params[2..-1])
      end

      false
    end # def _proxy_conn_324_received

    # RPL_TOPIC
    def _proxy_conn_332_received(ret, key, *args)
      msg = args[0]
      msg.suffix = @suffixmap[key]

      if msg.channel.nil? then
        bug("Empty channel at RPL_TOPIC: %s", msg)
      elsif !@state.channels.include?(msg.channel({:suffix=>true})) then
        bug("Unknown channel at RPL_TOPIC: %s, but %s", msg.channel({:suffix=>true}), @state.channels)
      else
        @state.channel(msg.channel({:suffix=>true})).topic = msg.params(2)
      end

      false
    end # def _proxy_conn_332_received

    # RPL_NAMREPLY
    def _proxy_conn_353_received(ret, key, *args)
      msg = args[0]
      msg.suffix = @suffixmap[key]

      if msg.channel.nil? then
        bug("Empty channel at RPL_NAMREPLY: %s", msg)
      elsif !msg.params(-1).gsub(/[@+]/, '').split(' ').include?(@state.nick) then
        # this may be the result of NAMES. and not relevant to you at all.
      elsif !@state.channels.include?(msg.channel({:suffix=>true})) then
        bug("Unknown channel at RPL_NAMREPLY: %s, but %s", msg.channel({:suffix=>true}), @state.channels)
      else
        cs = @state.channel(msg.channel({:suffix=>true}))

        if msg.params(1) == '@' then
          cs.set_mode('+s')
        elsif msg.params(1) == '*' then
          cs.set_mode('+p')
        end

        cs.clear_oper
        cs.clear_voice

        msg.params(-1).split(' ').each do |n|
          nick = n.sub(/^[@+]/, '')
          unless cs.joined?(nick) then
            cs.join(nick)
          end
          if n=~ /\A@/ then #
            cs.set_oper(nick, true)
          end
          if n=~ /\A\+/ then #
            cs.set_voice(nick, true)
          end
        end
      end

      false
    end # def _proxy_conn_353_received

    # RPL_BANLIST
    def _proxy_conn_367_received(ret, key, *args)
      msg = args[0]
      mask = msg.params(2)
      msg.suffix = @suffixmap[key]

      if msg.channel.nil? then
        bug("Empty channel at RPL_BANLIST: %s", msg)
      elsif !@state.channels.include?(msg.channel({:suffix=>true})) then
        bug("Unknown channel at RPL_BANLIST: %s, but %s", msg.channel({:suffix=>true}), @state.channels)
      else
        cs = @state.channel(msg.channel({:suffix=>true}))
        cs.set_mode('+b', mask)
      end

      false
    end # def _proxy_conn_367_received

    # ERR_INVALIDUSERNAME
    def _proxy_conn_468_received(ret, key, *args)
      FIXME("ERR_INVALIDUSERNAME")

      false
    end # def _proxy_conn_468_received

    def _proxy_conn_join_received(ret, key, *args)
      msg = args[0]
      msg.suffix = @suffixmap[key]

      if msg.channel.nil? then
        bug("Empty channel at JOIN: %s", msg)
      elsif msg.nick.nil? then
        bug("Empty nick name in the message at JOIN: %s", msg)
      else
        @state.join(msg.channel({:suffix=>true}), msg.nick)
      end

      false
    end # def _proxy_conn_join_received

    def _proxy_conn_kick_received(ret, key, *args)
      msg = args[0]
      msg.suffix = @suffixmap[key]

      if msg.channel.nil? then
        bug("Empty channel at KICK: %s", msg)
      elsif !@state.channels.include?(msg.channel({:suffix=>true})) then
        bug("Unknown channel at KICK: %s, but %s", msg.channel({:suffix=>true}), @state.channels)
      elsif msg.nick.nil? then
        bug("Empty nick name in the message at KICK: %s", msg)
      else
        @state.leave(msg.channel({:suffix=>true}), msg.params(1))
      end

      false
    end # def _proxy_conn_kick_received

    def _proxy_conn_mode_received(ret, key, *args)
      msg = args[0]
      msg.suffix = @suffixmap[key]

      if msg.channel.nil? then
        bug("Empty channel at MODE: %s", msg)
      elsif !@state.channels.include?(msg.channel({:suffix=>true})) then
        bug("Unknown channel at MODE: %s, but %s", msg.channel({:suffix=>true}), @state.channels)
      else
        cs = @state.channel(msg.channel({:suffix=>true}))
        cs.set_mode(*msg.params[1..-1])
      end

      false
    end # def _proxy_conn_mode_received

    def _proxy_conn_nick_received(ret, key, *args)
      msg = args[0]
      msg.suffix = @suffixmap[key]

      @state.channels.each do |ch|
        cs = @state.channel(ch)
        if cs.joined?(msg.nick) then
          mode.change_nick(msg.nick, msg.params(0))
        end
      end

      false
    end # def _proxy_conn_nick_received

    def _proxy_conn_part_received(ret, key, *args)
      msg = args[0]
      msg.suffix = @suffixmap[key]

      if msg.channel.nil? then
        bug("Empty channel at PART: %s", msg)
      elsif msg.nick.nil? then
        bug("Empty nick name in the message at PART: %s", msg)
      else
        @state.leave(msg.channel({:suffix=>true}), msg.nick)
      end

      false
    end # def _proxy_conn_part_received

    def _proxy_conn_quit_received(ret, key, *args)
      msg = args[0]
      msg.suffix = @suffixmap[key]

      if msg.nick.nil? then
        bug("Empty nick name in the message at QUIT: %s", msg)
      else
        @state.quit(msg.nick, msg.suffix)
      end

      false
    end # def _proxy_conn_quit_received

    def _proxy_conn_topic_received(ret, key, *args)
      msg = args[0]
      msg.suffix = @suffixmap[key]

      if msg.channel.nil? then
        bug("Empty channel at TOPIC: %s", msg)
      else
        @state.channel(msg.channel({:suffix=>true})).topic = msg.params(2)
      end

      false
    end # def _proxy_conn_topic_received

    def _proxy_listen_async(ret, key, *args)
      ctime = Time.now

      @value[:listener_timer].dup.each do |k, v|
        if ctime - v > @disconntimer then
          # must be deleted first from the list, because 'close' may takes a while.
          @value[:listener_timer].delete(k)
          begin
            @lsockets[k].close
          rescue IOError
            # just ignore it.
          end
        end
      end
      @lstate.dup.each do |k, v|
        if v.loggedin? then
          t = Time.now

          unless @value.has_key?(:ping) then
            @value[:ping] = {}
          end
          unless @value[:ping].has_key?(k) then
            @value[:ping][k] = PRUNE::TYPE::PingInfoStruct.new(0, t - 301, nil)
          end
          if @value[:ping][k].pinged_time.kind_of?(Time) then
            if t - @value[:ping][k].pinged_time > 180 then
              ret.emit_message(k, :Sent, false, 
                               PRUNE::Message::ERROR.new(sprintf("Ping timeout: %d seconds", t - @value[:ping][k].pinged_time)))
              # to avoid flooding.
              @value[:ping][k].pinged_time = Time.now
            end
          else
            if t - @value[:ping][k].timer > 300 then
              msg = PRUNE::Message::PING.new(v.nick)
              ret.emit_message(k, :Sent, false, msg)
              @value[:ping][k].pinged_time = t
              @value[:ping][k].signature = v.nick
            end
          end
        end
      end

      false
    end # def _proxy_listen_async

    def _proxy_listen_connected(ret, key, *args)
      debug('proxy/debug', "Disabling the flood control for %s", key)
      args[0].floodcontrol = false
      lkey = args[0].key

      @proxysigids[lkey] = @eventmgr.auto_registration(lkey, self, '_proxy_listen', PRUNE::EventManager::PRIOR_SYS_SYNC_NORMAL)
      @proxysigids["#{lkey}:parent"] = @eventmgr.register(lkey, :Received, PRUNE::EventManager::PRIOR_SYS_SYNC_HIGH, self, :_received)
      @lstate[lkey] = @state.dup
      # once reset all states
      @lstate[lkey].connected = false
      @lstate[lkey].connected = true
      @lstate[lkey].authenticated = (@listeninfo.options.has_key?('Password') && !@listeninfo.options['Password'].nil? ? false : true)
      @lstate[lkey].host = args[0].peeraddr[2]
      @value[:listener_timer][lkey] = Time.now
      @lsockets[lkey] = args[0]

      false
    end # def _proxy_listen_connected

    def _proxy_listen_disconnected(ret, key, *args)
      # don't kill this thread when it finished.
      Thread.current['keepalive'] = true

      @lstate[key].connected = false
      @lstate.delete(key)
      @value[:listener_timer].delete(key)
      @value[:ping].delete(key)
      @lsockets.delete(key)
      @eventmgr.auto_unregistration(@proxysigids[key])
      @eventmgr.unregister(@proxysigids["#{key}:parent"])
      @proxysigids.delete(key)
      @proxysigids.delete("#{key}:parent")

      false
    end # def _proxy_listen_disconnected

    def _proxy_listen_lloggedin(ret, key, *args)
      mkey = args[0]

      @value[:listener_timer].delete(key)
      @lstate[key].loggedin = true
      q = []
      q << PRUNE::Message::RPL_WELCOME.new(@lstate[key].nick,
                                           sprintf("Welcome to the Internet Relay Network, %s!%s@%s",
                                                   @lstate[key].nick, @lstate[key].user, @lstate[key].host))
      q << PRUNE::Message::RPL_YOURHOST.new(@lstate[key].nick,
                                            sprintf("Your host is %s, running version %s",
                                                    @lstate[key].host, PRUNE.version))
      q << PRUNE::Message::RPL_CREATED.new(@lstate[key].nick,
                                           sprintf("This server was created %s", PRUNE.date))
      q << PRUNE::Message::RPL_MYINFO.new(@lstate[key].nick, @lstate[key].host, PRUNE.dotversion,
                                          PRUNE::ModeList.availmode.split(//).sort.join,
                                          PRUNE::ChannelModeList.availmode.split(//).sort.join,
                                          nil)
      q << PRUNE::Message::RPL_MOTDSTART.new(@lstate[key].nick,
                                             sprintf("- %s Message of the day -", @lstate[key].host))
      q << PRUNE::Message::RPL_ENDOFMOTD.new(@lstate[key].nick, "End of MOTD command")
      if @state.nick != @lstate[key].nick then
        q << PRUNE::Message::NICK.new(@state.nick)
      end
      # flush the queue once to ensure the nick change.
      q.each do |m|
        ret.synchronize do |r|
          r.emit_message(key, :Sent, false, m)
        end
      end
      q = []
      @state.channels.each do |ch|
        q << PRUNE::Message::JOIN.new(ch, nil)
        cs = @state.channel(ch)
        if !cs.topic.nil? && !cs.topic.empty? then
          q << PRUNE::Message::RPL_TOPIC.new(@state.nick, ch, cs.topic)
        end
        type = '='
        type = '@' if cs.mode(PRUNE::ChannelModeList::MODE_SECRET) == true
        type = '*' if cs.mode(PRUNE::ChannelModeList::MODE_PRIVATE) == true
        nicks = cs.opers.map {|n| "@#{n}"}
        nonopers = cs.nicks.reject {|n| cs.opers.include?(n)}
        nicks.push(*nonopers)
        unless nicks.empty? then
          q << PRUNE::Message::RPL_NAMREPLY.new(@state.nick, type, ch, nicks.join(' '))
        end
        q << PRUNE::Message::RPL_ENDOFNAMES.new(@state.nick, ch, "End of NAMES list")
        mode = cs.mode_string.sub(/\A\+/, '').split(' ')
        ms = mode.shift
        ms = ms.split(//).delete_if do |x|
          if x == 'o' || x == 'v' then
            mode.shift
            true
          else
            false
          end
        end.join
        mode.unshift(ms)
        q << PRUNE::Message::RPL_CHANNELMODEIS.new(@state.nick, ch, "+" << mode.join(' '), nil)
      end

      q.each do |m|
        ret.synchronize do |r|
          r.emit_message(key, :Sent, false, m)
        end
      end

      false
    end # def _proxy_listen_lloggedin

    def _proxy_listen_received(ret, key, *args)
      return false if key == @sdelegate.key
      return false unless args[0].kind_of?(PRUNE::Message::Core)

      if self.is_listener?(key) then
        msg = args[0]

        # send a message to the master listener instance to take care of plugins.
        ret.synchronize do |x|
          v = ret.emit_message(@sdelegate.key, :Received, false, msg)
          if v[0] == true then
            return true
          end
        end

        if @lstate[key].loggedin? then
          ckey = @suffixreversemap[msg.suffix]
          # reset prefix to have the certain prefix when forwarding.
          msg.nick = nil
          msg.user = nil
          msg.host = nil
          msg.suffix = nil

          if ckey.nil? then
            # broadcast a message to all servers.
            @connectors.each do |k, v|
              v.emit_message(k, :Sent, false, msg)
            end
          else
            @connectors[ckey].emit_message(ckey, :Sent, false, msg)
          end
        end

        false
      else
        bug("Got :Received signal from the unknown origin: %s", key)
        true
      end
    end # def _proxy_listen_received

    def _proxy_listen_nick_received(ret, key, *args)
      msg = args[0]
      retval = false

      unless @lstate[key].loggedin? then
        retval = true
        if msg.params.empty? || msg.params(0).nil? then
          m = PRUNE::Message::ERR_NEEDMOREPARAMS.new(sprintf("%s :Not enough parameters", msg.command))
          m.enforced_delivery = true
          ret.emit_message(key, :Sent, false, m)

          return true
        else
          @lstate[key].nick = msg.params(0)
        end
        unless @lstate[key].user.nil? then
          unless @lstate[key].authenticated? then
            q = []
            q << PRUNE::Message::ERR_PASSWDMISMATCH.new('Password incorrect')
            q << PRUNE::Message::ERROR.new(sprintf("Closing Link: %s!%s@%s (Bad Password)", @lstate[key].nick, @lstate[key].user, @lstate[key].host))
            q.each do |m|
              m.enforced_delivery = true
              ret.emit_message(key, :Sent, false, m)
            end
          else
            ret.emit(key, :ListenerLoggedin, nil, @sdelegate.key)
          end
        end
      end

      retval
    end # def _proxy_listen_nick_received

    def _proxy_listen_notice_received(ret, key, *args)
      @lstate.dup.each do |k, v|
        if key != k && v.loggedin? then
          ret.emit_message(k, :Sent, false, args[0])
        end
      end

      false
    end # def _proxy_listen_notice_received

    def _proxy_listen_ping_received(ret, key, *args)
      msg = args[0]
      t = Time.now

      unless @value.has_key?(:ping) then
        @value[:ping] = {}
      end
      unless @value[:ping].has_key?(key) then
        @value[:ping][key] = PRUNE::TYPE::PingInfoStruct.new(0, t - 301, nil)
      end
      if !msg.params(1).nil? && !msg.params(1).empty? then
        if msg.params(1) == @state.nick then
          ret.emit_message(key, :Sent, false, PRUNE::Message::PONG.new(msg.params(1), msg.params(0)))
          @value[:ping][key].pinged_time = nil
          @value[:ping][key].timer = t
          @value[:ping][key].signature = nil
          true
        else
          false
        end
      else
        ret.emit_message(key, :Sent, false, PRUNE::Message::PONG.new(msg.params(0)))
        @value[:ping][key].pinged_time = nil
        @value[:ping][key].timer = t
        @value[:ping][key].signature = nil
        true
      end
    end # def _proxy_listen_ping_received

    def _proxy_listen_pong_received(ret, key, *args)
      msg = args[0]
      msg.suffix = @suffixmap[key]

      t = Time.now

      unless @value.has_key?(:ping) then
        @value[:ping] = {}
      end
      unless @value[:ping].has_key?(key) then
        @value[:ping][key] = PRUNE::TYPE::PingInfoStruct.new(0, t - 301, nil)
      end
      if !msg.params(1).nil? && !msg.params(1).empty? then
        if msg.params(1) == @value[:ping][key].signature then
          @value[:ping][key].pinged_time = nil
          @value[:ping][key].timer = t
          @value[:ping][key].signature = nil
          true
        else
          false
        end
      else
        if msg.params(0) == @value[:ping][key].signature then
          @value[:ping][key].pinged_time = nil
          @value[:ping][key].timer = t
          @value[:ping][key].signature = nil
          true
        else
          false
        end
      end
    end # def _proxy_listen_pong_received

    def _proxy_listen_privmsg_received(ret, key, *args)
      @lstate.dup.each do |k, v|
        if key != k && v.loggedin? then
          ret.emit_message(k, :Sent, false, args[0])
        end
      end

      false
    end # def _proxy_listen_privmsg_received

    def _proxy_listen_pass_received(ret, key, *args)
      msg = args[0]

      unless @lstate[key].loggedin? then
        if @listeninfo.options['Password'].nil? then
          debug('proxy/listener', "No Password section registered. just authenticate the connection from %s", key)
          @lstate[key].authenticated = true
        else
          if msg.params.empty? then
            m = PRUNE::Message::ERR_NEEDMOREPARAMS.new(sprintf("%s :Not enough parameters", msg.command))
            m.enforced_delivery = true
            ret.emit_message(key, :Sent, false, m)

            return true
          elsif @lstate[key].authenticated? then
            m = PRUNE::Message::ERR_ALREADYREGISTERED.new('Already registered')
            m.enforced_delivery = true
            ret.emit_message(key, :Sent, false, m)

            return true
          elsif msg.params(0) == @listeninfo.options['Password'] then
            debug('proxy/listener', "authenticated the connection from %s", key)
            @lstate[key].authenticated = true
          end
        end
      else
        ret.emit_message(key, :Sent, false,
                         PRUNE::Message::ERR_ALREADYREGISTERED.new('Already registered'))
        return true
      end

      false
    end # def _proxy_listen_pass_received

    def _proxy_listen_quit_received(ret, key, *args)
      if key == @sdelegate.key then
        bug("Server socket received QUIT message")
      else
        @value[:listener_timer][key] = Time.now - @disconntimer
      end

      true
    end # def _proxy_listen_quit_received

    def _proxy_listen_user_received(ret, key, *args)
      msg = args[0]

info("key: %s", key)
      unless @lstate[key].loggedin? then
        retval = true
        if msg.params.length < 4 then
          m = PRUNE::Message::ERR_NEEDMOREPARAMS.new(sprintf("%s :Not enough parameters", msg.command))
          m.enforced_delivery = true
          ret.emit_message(key, :Sent, false, m)

          return true
        end
        if @lstate[key].user.nil? then
          @lstate[key].user = msg.params(0)
          FIXME("user mode: %s", msg.params(1))
          @lstate[key].name = msg.params(3)
        end
        unless @lstate[key].nick.nil? then
          unless @lstate[key].authenticated? then
            q = []
            q << PRUNE::Message::ERR_PASSWDMISMATCH.new('Password incorrect')
            q << PRUNE::Message::ERROR.new(sprintf("Closing Link: %s!%s@%s (Bad Password)", @lstate[key].nick, @lstate[key].user, @lstate[key].host))
            q.each do |m|
              m.enforced_delivery = true
              ret.emit_message(key, :Sent, false, m)
            end
          else
            ret.emit(key, :ListenerLoggedin, nil, @sdelegate.key, key)
          end
        end
      else
        ret.emit_message(key, :Sent, false,
                         PRUNE::Message::ERR_ALREADYREGISTERED.new('Already registered'))
      end

      true
    end # def _proxy_listen_user_received

    def _proxy_listen_sent(ret, key, *args)
      return false if key == @sdelegate.key

      msg = args[0]

      if msg.command =~ /[0-9]+/ then #
        msg.host = @state.host if msg.host.nil?
      else
        if msg.host.nil? then
          msg.nick = @lstate[key].nick.dup if msg.nick.nil?
          msg.user = @lstate[key].user.dup if msg.user.nil?
          msg.host = @lstate[key].host.dup
        end
      end

      unless msg.kind_of?(PRUNE::Message::RAW) then
        # don't send a message as subsignal if the message is RAW.
        # this introduces a infinite loop in the event delivery.
        ret.emit_message(key, :Sent, true, msg)
      end

      return false if !@lstate[key].loggedin? && !msg.is_enforced_delivery?

      convert_from_utf8(msg) do |m|
        begin
          if @lsockets[key].nil? then
            bug("No such socket available: %s", key)
          else
            @lsockets[key].puts(m.to_s)
          end
        rescue IOError, SocketError, Errno::EPIPE, Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ETIMEDOUT, Errno::EHOSTUNREACH
          ret.emit(key, :Disconnected, nil, @lsockets[key])
        end
        if m.command == 'ERROR' then
          @value[:listener_timer][key] = Time.now - @disconntimer
        end
      end

      false
    end # def _proxy_listen_sent

    def _proxy_listen_nick_sent(ret, key, *args)
      msg = args[0]

      if msg.nick == @lstate[key].nick then
        @lstate[key].nick = msg.params(0)
      end

      false
    end # def _proxy_listen_nick_sent

  end # class IRCProxy

  class ProxyConfig
    include PRUNE::Debug

    ConfigInfoStruct = Struct.new(:filename, :mtime, :size, :data)

    UPDATE_MTIME = 0
    UPDATE_SIZE = 1
    UPDATE_MANUAL = 2

    def initialize
      @debug = false
      @config = nil
      @updatemode = UPDATE_MTIME
    end # def initialize

    attr_accessor :updatemode

    def debug=(flag)
      $DEBUG = flag
      @debug = flag

      Thread.abort_on_exception = (flag == true)
      PRUNE::Parser.ignore_invalid_message = (flag == true)
    end # def debug=

    def is_debug_enabled?
      @debug
    end # def is_debug_enabled?

    def load(file)
      @config = PRUNE::ProxyConfig::ConfigInfoStruct.new(file)

      unless File.exist?(@config.filename) then
        @config = nil
        raise ArgumentError, sprintf("No such configuration file available: %s", file)
      else
        @config.mtime = File.mtime(file)
        @config.size = File.size(file)
        File.open(file) do |f|
          @config.data = YAML.load(f.read)
        end
      end
    end # def load

    def get_connectors_config
      retval = []

      if @config.nil? then
        raise RuntimeError, "Must load the configuration file first."
      end
      pwent = Etc.getpwuid
      server_list.each do |k|
        next if @config.data[k].has_key?('Listen') && @config.data[k]['Listen'] == true

        args = []
        unless @config.data[k].has_key?('Nick') then
          raise RuntimeError, sprintf("No nickname defined in Nick section for the connector `%s'", k)
        end
        args << @config.data[k]['Nick']
        unless @config.data[k].has_key?('User') then
          user = pwent.name
          if @config.data[k].has_key?('UseCryptedName') &&
              @config.data[k]['UseCryptedName'] then
            user = user.crypt(user.object_id)
          end
        else
          user = @config.data[k]['User']
        end
        args << user
        unless @config.data[k].has_key?('Name') then
          n = pwent.gecos.sub(/,.*/, '')
          if n.nil? || n.empty? then
            name = "Prune User"
          else
            name = n
          end
        else
          name = @config.data[k]['Name']
        end
        args << name
        args << @config.data[k]
        args << @config.data[k]['Identity'] if @config.data[k].has_key?('Identity')

        data = []
        @config.data['ServerList'][k].each do |n|
          host, ports = n.split('/')
          ports.split(',').each do |port|
            data << PRUNE::TYPE::HostInfoStruct.new(host, port.to_i, *args)
          end
        end
        if data.empty? then
          raise RuntimeError, sprintf("No host/port information for `%s'", k)
        end

        retval << data
      end

      retval
    end # def get_connectors_config

    def get_listener_config
      data = nil
      name = nil

      if @config.nil? then
        raise RuntimeError, "Must load a configuration file first."
      end
      server_list.each do |k|
        if @config.data[k].has_key?('Listen') && @config.data[k]['Listen'] == true then
          name = k
          data = @config.data[k]
          break
        end
      end
      if data.nil? then
        raise RuntimeError, sprintf("No Listener configuration available in `%s'", @config.filename)
      end

      n = @config.data['ServerList'][name][0]
      host, port = n.split('/')
      args = [host, port.to_i]

      unless @config.data[name].has_key?('Nick') then
        raise RuntimeError, sprintf("No nickname defined in Nick section for the listener.")
      else
        args << @config.data[name]['Nick']
      end
      pwent = Etc.getpwuid
      unless @config.data[name].has_key?('User') then
        user = pwent.name
        args << user.crypt(user.object_id)
      else
        args << @config.data[name]['User']
      end
      unless @config.data[name].has_key?('Name') then
        n = pwent.gecos.sub(/,.*/, '') 
        if n.nil? || n.empty? then
          args << 'Prune User'
        else
          args << n
        end
      else
        args << @config.data[name]['Name']
      end
      args << @config.data[name]

      return PRUNE::TYPE::HostInfoStruct.new(*args)
    end # def get_listener_config

    private

    def server_list
      retval = []

      if @config.nil? then
        raise RuntimeError, "Must load the configuration file first."
      end

      if @config.data.has_key?('ServerList') then
        @config.data['ServerList'].each_key do |k|
          if @config.data.has_key?(k) then
            retval << k
          end
        end
      end

      retval
    end # def server_list

  end # class ProxyConfig

end # module PRUNE

if $0 == __FILE__ then
  smgr = PRUNE::SocketManager.instance
  if ENV.has_key?('PRUNE_DEBUG') then
    ENV['PRUNE_DEBUG'].split(',').each do |v|
      smgr.category_list.add(v)
    end
  end

  conf = PRUNE::ProxyConfig.new

  begin
    ARGV.options do |opt|
      opt.on('--config=FILE', 'read FILE as the configuration file.') {|v| conf.load(v)}
      opt.on('-d', '--debug', 'turn on the debugging mode') {|v| conf.debug = true}

      opt.parse!
    end
  rescue => e
    p e
    exit
  end

  if conf.is_debug_enabled? then
    path = File.join(ENV['HOME'], '.prune')
    unless File.exist?(path) then
      FileUtils.mkdir(path)
    end
    def PRUNE.fixme_output(msg)
      path = File.join(ENV['HOME'], '.prune', Time.now.strftime("prune_fixme_%Y%m%d.log"))
      File.open(path, "a") do |x|
        x.write(sprintf("%s\n", msg))
      end
    end # def 
    def PRUNE.bug_output(msg)
      path = File.join(ENV['HOME'], '.prune', Time.now.strftime("prune_bug_%Y%m%d.log"))
      File.open(path, "a") do |x|
        x.write(sprintf("%s\n", msg))
      end
    end # def 
    def PRUNE.warning_output(msg)
      path = File.join(ENV['HOME'], '.prune', Time.now.strftime("prune_warning_%Y%m%d.log"))
      File.open(path, "a") do |x|
        x.write(sprintf("%s\n", msg))
      end
    end # def 
  end

  cl = conf.get_listener_config
  cc = conf.get_connectors_config

  irc = PRUNE::IRCProxy.new

  class Foo
    include PRUNE::Debug

    def output(ret, key, *args)
      msg = args[0]
      msg.extend(PRUNE::Decoration)
      printf("%s", msg.to_s)

      false
    end # def 

    def stat(ret, key, *args)
      printf("  %s <%s!%s@%s>\n", ret.state.name, ret.state.nick, ret.state.user, ret.state.host)
      printf("  Mode: %s\n", ret.state.mode_string)
      printf("  Channels:\n")
      ret.state.channels.each do |ch|
        printf("    %s: topic - %s\n", ch, ret.state.channel(ch).topic)
        printf("        mode - %s\n", ret.state.channel(ch).mode_string)
        printf("        %s\n", ret.state.channel(ch).nicks.join(','))
      end

      false
    end # def 

    def send_join(ret, key, *args)
      ret.emit_message(key, :Sent, false, PRUNE::Message::JOIN.new("#rail,#prune",nil))#,#せつない", nil))

      false
    end # def 

  end # class 

=begin
  x = Foo.new
  irc.register(:Received, PRUNE::EventManager::PRIOR_USR_SYNC_NORMAL, x, :output)
  irc.register(:Sent, PRUNE::EventManager::PRIOR_USR_SYNC_NORMAL, x, :output)
  irc.register(:Received, PRUNE::EventManager::PRIOR_USR_SYNC_LOW, x, :stat)
  irc.register(:Loggedin, PRUNE::EventManager::PRIOR_USR_SYNC_NORMAL, x, :send_join)
=end

  trap("SIGINT") do
    class ErrorSIGINT < StandardError; end

    begin
      raise ErrorSIGINT
    rescue => e
      printf("%s\n", e.backtrace.join("\n"))
    end
    irc.synchronize do |x|
      x.connectors.each do |k, v|
        v.emit_message(nil, :Sent, false, PRUNE::Message::QUIT.new(sprintf("Prune %s", PRUNE.version)))
        v.emit_message(nil, :Sent, false, PRUNE::Message::PING.new("prune"))
      end
    end
    irc.close
    printf("Successfully closed.\n")
    exit!
  end
  trap("SIGTERM") do
    irc.synchronize do |x|
      x.connectors.each do |k, v|
        v.emit_message(nil, :Sent, false, PRUNE::Message::QUIT.new(sprintf("Prune %s", PRUNE.version)))
        v.emit_message(nil, :Sent, false, PRUNE::Message::PING.new("prune"))
      end
    end
    irc.close
    printf("Successfully closed.\n")
    exit!
  end
  irc.open(cc, cl) do |ev, stat, key|
  end
end
